package edu.pku.sei.mte.modelmatch;

import static choco.Choco.eq;
import static choco.Choco.makeIntVar;
import static choco.Choco.neq;
import static choco.Choco.or;
import static choco.Choco.relationPairAC;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import choco.cp.model.CPModel;
import choco.cp.solver.CPSolver;
import choco.kernel.model.Model;
import choco.kernel.model.variables.integer.IntegerVariable;
import edu.pku.sei.mte.constants.MDAConstant;
import edu.pku.sei.mte.mtemodel.metamodel.RTypeClass;
import edu.pku.sei.mte.mtemodel.metamodel.RTypeGraph;
import edu.pku.sei.mte.mtemodel.model.MClass;
import edu.pku.sei.mte.mtemodel.model.MModelGraph;
import edu.pku.sei.mte.mtemodel.model.MReference;
import edu.pku.sei.mte.mtemodel.mtmodel.common.Variable;
import edu.pku.sei.mte.mtemodel.mtmodel.expression.Expression;
import edu.pku.sei.mte.mtemodel.mtmodel.pattern.TPatternEdge;
import edu.pku.sei.mte.mtemodel.mtmodel.pattern.TPatternGraph;
import edu.pku.sei.mte.mtemodel.mtmodel.pattern.TPatternNode;
import edu.pku.sei.mte.runtime.Context;
import edu.pku.sei.mte.runtime.rulebased.RBTContext;
import edu.pku.sei.mte.transformation.VisitorFactory;
import edu.pku.sei.mte.transformation.expression.EvaluateExpressionVisitor;

public class ChocoModelMatchEngine {
	
	public static ChocoModelMatchEngine INSTANCE = new ChocoModelMatchEngine();
	
	private Map<MModelGraph, ChocoMModelGraph> M2CMap = new HashMap<MModelGraph, ChocoMModelGraph>();
	private Map<TPatternGraph, MMChocoModel> P2MMap = new HashMap<TPatternGraph, MMChocoModel>();
	
	public List<Context> match(TPatternGraph pattern, MModelGraph model, Context base) throws Exception {
		List<Context> contexts = new ArrayList<Context>();
			
		MMChocoModel mmcm = P2MMap.get(pattern);
		if(mmcm == null){
			ChocoMModelGraph cmodel = M2CMap.get(model);
			if(cmodel == null){
				cmodel = new ChocoMModelGraph(model);
				if(!base.getEnvironment().isTargetModel(model))
					M2CMap.put(model, cmodel);
			}
			mmcm = new MMChocoModel(pattern, cmodel);
			if(!base.getEnvironment().isTargetModel(model))
				P2MMap.put(pattern, mmcm);
		}
		Model m = mmcm.getChocoModel(base);
		if(m == null)
			return contexts;
		CPSolver s = new CPSolver();
        s.read(m);
        s.solve();
        if(s.isFeasible()){
        	do {
        		Context result = (Context) base.clone();
        		for(int i=0;i<mmcm.PNC;i++){
        			TPatternNode n = pattern.getNodes().get(i);
        			MClass mn = model.getNodes().get(s.getVar(mmcm.PN[i]).getVal());
        			result.setVariable(n.getName(), mn);
        		}
        		for(int i=0;i<mmcm.PEC;i++){
        			TPatternEdge e = pattern.getEdges().get(i);
        			MReference mr = model.getEdges().get(s.getVar(mmcm.PE[i]).getVal());
        			result.setVariable(e.getName(), mr);
        		}
        		if(checkNodeExpr(pattern,result)){
        			contexts.add(result);
				}
        	} while(s.nextSolution());
        }
		return contexts;
	}
	
	private boolean checkNodeExpr(TPatternGraph pattern, Context result) throws Exception {
		EvaluateExpressionVisitor eev = VisitorFactory.INSTANCE.getCheckOnlyExpressionVisitor((RBTContext) result);
		List<Expression> exprs = pattern.getTemplateExpressions();
		for(Expression e : exprs){
			eev.setInitialExpectedValue(null);
			Object res = e.accept(eev);//e.evaluate(result, false);
			if(MDAConstant.isFalse(res)) {
				return false;
			}
		}
		return true;
	}
	
	public List<Context> matchWithoutOptimization(TPatternGraph pattern, MModelGraph model, Context base) throws Exception {
		List<Context> contexts = new ArrayList<Context>();
		int nodeCount = model.getNodes().size();
		int edgeCount = model.getEdges().size();
		int PNC = pattern.getNodes().size();
		int PEC = pattern.getEdges().size();
		if(nodeCount < PNC || edgeCount < PEC)
			return contexts;
		if(base==null)
			base = new Context();
		//initial
		int[] nodeTypeArray = new int[nodeCount];
		int[] edgeTypeArray = new int[edgeCount];
		int[] edgeSourceArray = new int[edgeCount];
		int[] edgeTargetArray = new int[edgeCount];
		int[] edgeSourceOrderArray = new int[edgeCount];
		int[] edgeTargetOrderArray = new int[edgeCount];
		RTypeGraph typeGraph = model.getTypeGraph();
		for(int i=0; i<nodeCount; i++){
			if(base.checkModelBindingExistIn(model.getNodes().get(i), model))
				nodeTypeArray[i] = -1;
			else nodeTypeArray[i] = typeGraph.getNodes().indexOf(model.getNodes().get(i).getType());
		}
		for(int i=0; i<edgeCount; i++){
			if(base.checkModelBindingExistIn(model.getEdges().get(i), model))
				edgeTypeArray[i] = -1;
			edgeTypeArray[i] = typeGraph.getEdges().indexOf(model.getEdges().get(i).getType());
			edgeSourceArray[i] = model.getNodes().indexOf(model.getEdges().get(i).getSource());
			edgeTargetArray[i] = model.getNodes().indexOf(model.getEdges().get(i).getTarget());
			edgeSourceOrderArray[i] = model.getEdges().get(i).getSourceOrder();
			edgeTargetOrderArray[i] = model.getEdges().get(i).getTargetOrder();
		}
		
		//model:
		Model m = new CPModel();
		IntegerVariable[] PN = new IntegerVariable[PNC];
		IntegerVariable[] PNType = new IntegerVariable[PNC];
        for (int i = 0; i < PNC; i++) {
            PN[i] = makeIntVar("PN" + i, 0, nodeCount-1, "cp:decision");
            List<Integer> typeArray = new ArrayList<Integer>();
            for(RTypeClass rtc : model.getTypeGraph().getNodes()){
            	if(rtc.isA(pattern.getNodes().get(i).getType())){
            		typeArray.add(model.getTypeGraph().getNodes().indexOf(rtc));
            	}
            }
            PNType[i] = makeIntVar("PNType" + i, typeArray, "cp:enum");
        }
        IntegerVariable[] PE = new IntegerVariable[PEC];
        IntegerVariable[] PEType = new IntegerVariable[PEC];
        IntegerVariable[] PES = new IntegerVariable[PEC];
        IntegerVariable[] PET = new IntegerVariable[PEC];
        IntegerVariable[] PESO = new IntegerVariable[PEC];
        IntegerVariable[] PETO = new IntegerVariable[PEC];
        IntegerVariable noOrder = makeIntVar("noorder", -1, 0, "cp:no_decision");
        for (int i = 0; i < PEC; i++) {
        	PE[i] = makeIntVar("PE" + i, 0, edgeCount-1, "cp:decision");
            PEType[i] = makeIntVar("PEType" + i, 0, typeGraph.getEdges().size()-1, "cp:no_decision");
            PES[i] = makeIntVar("PES" + i, 0, nodeCount-1, "cp:no_decision");
            PET[i] = makeIntVar("PET" + i, 0, nodeCount-1, "cp:no_decision");
            PESO[i] = makeIntVar("PESO" + i, -1, nodeCount-1, "cp:no_decision");
            PETO[i] = makeIntVar("PETO" + i, -1, nodeCount-1, "cp:no_decision");
        }
        //constraints:
        m.addConstraint(eq(noOrder, -1));
        for (int i = 0; i < PNC; i++) {
        	for(int j = i+1; j<PNC; j++)
        		m.addConstraint(neq(PN[i], PN[j]));
			Variable var = base.getVariable(pattern.getNodes().get(i).getName());
			if(var!=null&&var.getValue()!=Variable.UNINITIATED_VALUE){
				MClass mc = (MClass) var.getValue();
				m.addConstraint(eq(PN[i], model.getNodes().indexOf(mc)));
				m.addConstraint(eq(PNType[i], typeGraph.getNodes().indexOf(mc.getType())));
			} else {
				m.addConstraint(relationPairAC(PN[i], PNType[i], new MMArrayEqual(nodeTypeArray)));
//				m.addConstraint(eq(PNType[i], typeGraph.getNodes().indexOf(pattern.getNodes().get(i).getType())));
			}
        }
        for (int i = 0; i < PEC; i++) {
        	for(int j = i+1; j<PEC; j++)
        		m.addConstraint(neq(PE[i], PE[j]));
        	Variable var = base.getVariable(pattern.getEdges().get(i).getName());
			if(var!=null&&var.getValue()!=Variable.UNINITIATED_VALUE){
				MReference mr = (MReference) var.getValue();
				m.addConstraint(eq(PE[i], model.getEdges().indexOf(mr)));
				m.addConstraint(eq(PEType[i], typeGraph.getEdges().indexOf(mr.getType())));
				m.addConstraint(eq(PES[i], model.getNodes().indexOf(mr.getSource())));
				m.addConstraint(eq(PET[i], model.getNodes().indexOf(mr.getTarget())));
				m.addConstraint(eq(PESO[i], model.getEdges().get(i).getSourceOrder()));
				m.addConstraint(eq(PETO[i], model.getEdges().get(i).getTargetOrder()));
			} else {
				m.addConstraint(relationPairAC(PE[i], PEType[i], new MMArrayEqual(edgeTypeArray)));
				m.addConstraint(relationPairAC(PE[i], PES[i], new MMArrayEqual(edgeSourceArray)));
				m.addConstraint(relationPairAC(PE[i], PET[i], new MMArrayEqual(edgeTargetArray)));
				m.addConstraint(relationPairAC(PE[i], PESO[i], new MMArrayEqual(edgeSourceOrderArray)));
				m.addConstraint(relationPairAC(PE[i], PETO[i], new MMArrayEqual(edgeTargetOrderArray)));
				m.addConstraint(eq(PEType[i], typeGraph.getEdges().indexOf(pattern.getEdges().get(i).getType())));
				m.addConstraint(eq(PES[i], PN[pattern.getNodes().indexOf(pattern.getEdges().get(i).getSource())]));
				m.addConstraint(eq(PET[i], PN[pattern.getNodes().indexOf(pattern.getEdges().get(i).getTarget())]));
				m.addConstraint(or(eq(noOrder, pattern.getEdges().get(i).getSourceOrder()), eq(PESO[i], pattern.getEdges().get(i).getSourceOrder())));
				m.addConstraint(or(eq(noOrder, pattern.getEdges().get(i).getTargetOrder()), eq(PETO[i], pattern.getEdges().get(i).getTargetOrder())));
        	}
        }
		CPSolver s = new CPSolver();
        s.read(m);
        s.solve();
        if(s.isFeasible()){
        	do {
        		Context result = (Context) base.clone();
//        		System.out.print("[");
        		for(int i=0;i<PNC;i++){
//        			System.out.print(s.getVar(PN[i]).getVal()+ ",");
        			TPatternNode n = pattern.getNodes().get(i);
        			MClass mn = model.getNodes().get(s.getVar(PN[i]).getVal());
        			result.setVariable(n.getName(), mn);
        		}
//        		System.out.print("],[");
        		for(int i=0;i<PEC;i++){
//        			System.out.print(s.getVar(PE[i]).getVal()+ ",");
        			TPatternEdge e = pattern.getEdges().get(i);
        			MReference mr = model.getEdges().get(s.getVar(PE[i]).getVal());
        			result.setVariable(e.getName(), mr);
        		}
//        		System.out.println("]");
        		if(checkNodeExpr(pattern,result)){
        			contexts.add(result);
				}
        	} while(s.nextSolution());
        }
		return contexts;
	}
}
